@tool
class_name Utils

## 工具类，通用的单独方法可以放这

## 返回指定索引处的值，没有返回默认值
static func get_or_default(arr:Array,idx:int,default_value = null):
	if arr and idx >= 0 and arr.size() > idx:
		return arr[idx]
	else:
		return default_value
## 返回指定索引处的值，没有返回默认值
static func get_by_func_or_default(arr:Array,function:Callable,default_value = null):
	if arr:
		var res = arr.filter(function)
		if res.size() > 0:
			return res
		else:
			return default_value
	else:
		return default_value
## 根据枚举值得到键
static func get_enum_key_by_value(cur_enum,value) -> String:
	for key in cur_enum.keys():
		if cur_enum[key] == value:
			return key
	return ""
## 用指定连接符号连接数组中所有字符串
static func arr_str_join(arr:Array[String],conn_symbol:String = " "):
	var str = ""
	for i in range(arr.size()):
		str += "{0}{1}".format(["" if i == 0 else conn_symbol,arr[i]])
	return str
## 从arr中随机返回count个数据
static func random_get_count(arr:Array,count:int,is_repeat:bool,rng:GameRandomNumberGenerator = null) -> Array:
	var arr_copy:Array = arr.duplicate(true)
	if rng:
		Utils.shuffle(arr_copy,rng)
	else:
		arr_copy.shuffle()
	var arr_res:Array = []
	if is_repeat:
		for i in range(count):
			arr_res.append(arr_copy.pick_random())
	else:
		count = min(count,arr.size())
		for i in range(count):
			arr_res.append(arr_copy[i])
	return arr_res
## 根据可能性返回元素
static func random_get_count_by_probability(arr:Array,probabilities:Array,count:int,is_duplicate:bool) -> Array:
	var arr_copy = arr.duplicate(true)
	var probabilities_copy = probabilities.duplicate(true)
	var res_arr = []
	while res_arr.size() < count and arr_copy.size()>0:
		var total_probability = probabilities_copy.reduce(func (accum,number): return accum + number,null)
		var random_value = randi_range(1,total_probability)
		var t = 0
		for i in range(probabilities_copy.size()):
			var p = probabilities_copy[i]
			if random_value <= t:
				res_arr.append(arr_copy[i])
				if is_duplicate == false:
					arr_copy.remove_at(i)
					probabilities_copy.remove_at(i)
				break
			else:
				t += p
	if count == 1 and res_arr.size() == 1:
		return res_arr[0]
	return res_arr
## 根据随机种子打乱数组（原数组上操作）
static func shuffle(arr:Array,rng:GameRandomNumberGenerator = null):
	for i in range(arr.size()):
		if i == arr.size() - 1:
			break
		var random_idx = rng.grng_randi_range(i,arr.size() - i) if rng else randi_range(i,arr.size() - i)
		Utils.array_swap(arr,i,random_idx)
## 返回两点间的曼哈顿距离
static func get_manhatten_distance(a:Vector2,b:Vector2) -> float:
	return abs(a.x - b.x) + abs(a.y-b.y)
## 返回两点间的欧氏距离
static func get_euclidean_distance(a:Vector2,b:Vector2) -> float:
	return sqrt(pow(a.x-b.x,2) + pow(a.y-b.y,2))
## 返回两点间的对角线距离
## 参考URL：https://godorz.info/2009/11/heuristics-2/
static func get_diagonal_distance(a:Vector2,b:Vector2) -> float:
	var diagonal:float = min(abs(a.x-b.x),abs(a.y-b.y))
	var straight = abs(a.x-b.x) + abs(a.y-b.y)
	var d2 = sqrt(2)
	var h = d2 * diagonal + (straight - 2 * diagonal)
	return h
## 给某个Animation添加方法轨道以及方法
## animation_library_name: "" 表示全局动画组
## args: 传入是一个Array，但接受方必须将这个Array解包（就是去掉外面的一层[]），同时参数数量必须相等，即必须全部接受
static func add_call_method_track_and_method(animation_player:AnimationPlayer,anim_name:String,time:float,method_name:String,args:Array,bind_obj:Node) -> AddCallMethodTrackRes:
	var res = AddCallMethodTrackRes.new()
	var anim:Animation = animation_player.get_animation(anim_name)
	if not anim.resource_local_to_scene:
		anim = anim.duplicate(true)
		var library = animation_player.get_animation_library("")
		library.add_animation(anim_name,anim)
	var track_idx = anim.add_track(Animation.TYPE_METHOD)
	res["track_idx"] = track_idx
	var path = animation_player.get_node(animation_player.root_node).get_path_to(bind_obj)
	anim.track_set_path(track_idx,path)
	var key_idx = anim.track_insert_key(track_idx,time,{method = method_name, args = args})
	res["key_idx"] = key_idx
	res["remove_track"] = func():
		var cur_anim = animation_player.get_animation(anim_name)
		cur_anim.remove_track(track_idx)
	res["remove_key"] = func():
		var cur_anim = animation_player.get_animation(anim_name)
		cur_anim.track_remove_key(track_idx,key_idx)
	res["destroy"] = func():
		var cur_anim = animation_player.get_animation(anim_name)
		cur_anim.track_remove_key(track_idx,key_idx)
		cur_anim.remove_track(track_idx)
	return res
## 给某个Animation添加方法轨道
static func add_call_method_track(animation_player:AnimationPlayer,anim_name:String,bind_obj:Node) -> int:
	var anim:Animation = animation_player.get_animation(anim_name)
	if not anim.resource_local_to_scene:
		anim = anim.duplicate(true)
		var library = animation_player.get_animation_library("")
		library.add_animation(anim_name,anim)
	var track_idx = anim.add_track(Animation.TYPE_METHOD)
	var path = bind_obj.get_path()
	anim.track_set_path(track_idx,path)
	return track_idx
## 给某个Animation指定方法轨道添加方法
static func add_call_method_track_key(animation_player:AnimationPlayer,anim_name:String,track_idx:int,time:float,method_name:String,args:Array) -> AddCallMethodTrackRes:
	var res = AddCallMethodTrackRes.new()
	var anim = animation_player.get_animation(anim_name)
	if not anim.resource_local_to_scene:
		anim = anim.duplicate(true)
		var library = animation_player.get_animation_library("")
		library.add_animation(anim_name,anim)
	if anim.track_get_type(track_idx) != Animation.TrackType.TYPE_METHOD:
		return res
	var key_idx = anim.track_insert_key(track_idx,time,{method = method_name,args = args})
	res["track_idx"] = track_idx
	res["key_idx"] = key_idx
	res["remove_track"] = func():
		var cur_anim = animation_player.get_animation(anim_name)
		cur_anim.remove_track(track_idx)
	res["remove_key"] = func():
		var cur_anim = animation_player.get_animation(anim_name)
		cur_anim.track_remove_key(track_idx,key_idx)
	return res
## 获得圆内随机一点
static func get_random_point_in_circle(center:Vector2,radius:float) -> Vector2:
	var angle:float = GameManager.get_grng().grng_randf_range(0,TAU)
	var dis:float = GameManager.get_grng().grng_randf() * radius
	var x:float = cos(angle) * dis
	var y:float = sin(angle) * dis
	return center + Vector2(x,y)
## 获得圆上随机一点
static func get_random_point_on_circle(center:Vector2,radius:float) -> Vector2:
	var angle:float = GameManager.get_grng().grng_randf_range(0,TAU)
	var dis:float = radius
	var x:float = cos(angle) * dis
	var y:float = sin(angle) * dis
	return center + Vector2(x,y)
## 获得矩形内随机一点
static func get_random_point_in_rectangle(center:Vector2,size:Vector2) -> Vector2:
	var x:float = GameManager.get_grng().grng_randf_range(center.x - size.x,center.x + size.x)
	var y:float = GameManager.get_grng().grng_randf_range(center.y - size.y,center.y + size.y)
	return Vector2(x,y)
## 交互数组里两个元素的位置
static func array_swap(arr:Array,idx0:int,idx1:int):
	if idx0 < arr.size() and idx1 < arr.size():
		var temp = arr[idx0]
		arr[idx0] = arr[idx1]
		arr[idx1] = temp
## 获得对应索引的值，没有返回默认值
static func get_value_or_default(item,key,default=null):
	if key in item:
		return item[key]
	return default
## 把self_node的绝对位置调整到和target_node的绝对位置一样（即使得两者重合）
## 该方法支持两个物体不在同一个CanvasLayer下，即使其中一个跟随相机也可以
static func follow_target_absolute_position(self_node:Node2D,target_node:Node2D):
	var target_position = target_node.get_viewport_transform() * target_node.global_position
	self_node.global_position = self_node.get_viewport_transform().affine_inverse() * target_position
## 获得两个数组的交集
static func get_array_intersection(arr_0:Array,arr_1:Array) -> Array:
	var target_arr:Array = []
	var new_arr = arr_0 + arr_1
	for item in new_arr:
		if not target_arr.has(item) and arr_0.has(item) and arr_1.has(item):
			new_arr.append(item)
	return target_arr
## 获得两个数组的并集
static func get_array_union(arr_0:Array,arr_1:Array) -> Array:
	var target_arr:Array = []
	var new_arr = arr_0.duplicate()
	for item in arr_1:
		if not target_arr.has(item):
			new_arr.append(item)
	return target_arr
## 获得两个数组的差集
static func get_array_difference(arr_0:Array,arr_1:Array) -> Array:
	var target_arr:Array = []
	var new_arr = arr_0 + arr_1
	for item in new_arr:
		if not target_arr.has(item) and not (arr_0.has(item) and arr_1.has(item)):
			new_arr.append(item)
	return target_arr
## 对数组中的每个元素执行指定的回调函数，没有返回值
static func array_for_each(arr:Array,callback:Callable):
	for item in arr:
		callback.call(item)
## 分别执行数组中每个元素的指定名称方法，没有返回值
static func array_call_method(arr:Array,method:String,args = null):
	for item in arr:
		if item.has_method(method):
			if args != null:
				item.call(method,args)
			else:
				item.call(method)
## 获得root
static func get_root(node:Node):
	var root:Node = node
	while root.get_parent():
		root = root.get_parent()
	return root
## 确保指定的方法一定成功执行一次
static func promise_func_call_successful(f:Callable,attempt_amount:int = Constant.ATTEMPT_AMOUNT,check_interval:float = 0.02):
	while attempt_amount > 0:
		attempt_amount -= 1
		if f.call():
			return true
		await GameManager.get_tree().create_timer(check_interval).timeout
## 从指定对象中得到指定属性名的属性信息
static func get_property_info(target:Object,property_name:String) -> Dictionary:
	var info
	for p_info in target.get_property_list():
		if p_info.name == property_name:
			info = p_info
			break
	return info
#region 物体检测
## 圆形检测
## 检测某处有无障碍物
static func check_obstacle_circle(check_type_range:GameEnum.UtilObstacleCheckRange,layer_mask,pos:Vector2,check_radius:float = 2,return_collision_info:bool = false):
	var world2d = GameManager.physice_check_world_2d.get_world_2d()
	var state = world2d.direct_space_state
	# 圆形检测
	var shape_rid = PhysicsServer2D.circle_shape_create()
	var shape_data = check_radius
	PhysicsServer2D.shape_set_data(shape_rid,shape_data)
	var parameters = PhysicsShapeQueryParameters2D.new()
	if check_type_range == GameEnum.UtilObstacleCheckRange.Area:
		parameters.collide_with_areas = true
		parameters.collide_with_bodies = false
	elif check_type_range == GameEnum.UtilObstacleCheckRange.Body:
		parameters.collide_with_areas = false
		parameters.collide_with_bodies = true
	elif check_type_range == GameEnum.UtilObstacleCheckRange.AB:
		parameters.collide_with_areas = true
		parameters.collide_with_bodies = true
	parameters.collision_mask = layer_mask
	parameters.shape_rid = shape_rid
	parameters.transform = Transform2D(0,pos)
	var info = state.intersect_shape(parameters)
	if return_collision_info:
		return info
	return info and info.size() > 0
## 矩形检测
## 检测某处有无障碍物
## half_extents:半边距，默认值长宽都为1
static func check_obstacle_rectangle(check_type_range:GameEnum.UtilObstacleCheckRange,layer_mask,pos:Vector2,half_extents:Vector2 = Vector2(0.5,0.5),return_collision_info:bool = false):
	var world2d = GameManager.physice_check_world_2d.get_world_2d()
	var state = world2d.direct_space_state
	# 矩形检测
	var shape_rid = PhysicsServer2D.rectangle_shape_create()
	var shape_data = half_extents
	PhysicsServer2D.shape_set_data(shape_rid,shape_data)
	var parameters = PhysicsShapeQueryParameters2D.new()
	if check_type_range == GameEnum.UtilObstacleCheckRange.Area:
		parameters.collide_with_areas = true
		parameters.collide_with_bodies = false
	elif check_type_range == GameEnum.UtilObstacleCheckRange.Body:
		parameters.collide_with_areas = false
		parameters.collide_with_bodies = true
	elif check_type_range == GameEnum.UtilObstacleCheckRange.AB:
		parameters.collide_with_areas = true
		parameters.collide_with_bodies = true
	parameters.collision_mask = layer_mask
	parameters.shape_rid = shape_rid
	parameters.transform = Transform2D(0,pos)
	var info = state.intersect_shape(parameters)
	if return_collision_info:
		return info
	return info and info.size() > 0
## 射线检测
## exclude_nodes:忽略的对象列表
static func check_obstacle_ray(check_type_range:GameEnum.UtilObstacleCheckRange,layer_mask,from:Vector2,to:Vector2,exclude_nodes:Array[Node] = [],hit_from_inside:bool = false,return_collision_info:bool = false):
	var world2d = GameManager.physice_check_world_2d.get_world_2d()
	var state:PhysicsDirectSpaceState2D = world2d.direct_space_state

	var parameters = PhysicsRayQueryParameters2D.create(from,to,layer_mask)
	for node in exclude_nodes:
		var collision_object_2ds:Array[Node] = Utils.find_children_by_type(node,"CollisionObject2D",-1)
		for obj in collision_object_2ds:
			parameters.exclude.append(obj.get_rid())
	parameters.hit_from_inside = hit_from_inside
	if check_type_range == GameEnum.UtilObstacleCheckRange.Area:
		parameters.collide_with_areas = true
		parameters.collide_with_bodies = false
	elif check_type_range == GameEnum.UtilObstacleCheckRange.Body:
		parameters.collide_with_areas = false
		parameters.collide_with_bodies = true
	elif check_type_range == GameEnum.UtilObstacleCheckRange.AB:
		parameters.collide_with_areas = true
		parameters.collide_with_bodies = true
	var info = state.intersect_ray(parameters)
	if return_collision_info:
		return info
	return info and info.size() > 0
#endregion
#region GetNode
## 得到该类型的继承链（只包含自定义类型）
static func get_inheritance_chain(type:Script) -> Array:
	var inheritance_chain = []
	var temp_type = type
	while temp_type:
		inheritance_chain.append(temp_type)
		temp_type = temp_type.get_base_script()
	return inheritance_chain
## 得到该类型的继承链（字符串版本，会包含第一个非自定义类型）
static func get_inheritance_chain_str(type:Script) -> Array:
	var inheritance_chain_str = []
	var temp_type = type
	while temp_type:
		if temp_type.get_global_name() not in inheritance_chain_str:
			inheritance_chain_str.append(temp_type.get_global_name())
		temp_type = temp_type.get_base_script()
	var base_type:StringName = type.get_instance_base_type()
	if base_type and base_type not in inheritance_chain_str:
		inheritance_chain_str.append(base_type)
	return inheritance_chain_str
## 根据类型查找第一个子节点
## node:当前节点
## type:查找的类型，可以是具体类型（只支持自定义类，即节点附加这个脚本），也可以是字符串（只支持非自定义类型和自定义类型的严格类型检查）
## depth:查找深度，默认只查找自身，<0会递归查找直至没有节点，>0会查找对应深度的子节点
## is_strict_type_check:是否是严格类型检查，如果为true，则节点必须是相同类型，继承类将无法通过检查，该参数只有传入的type为具体类型时有效
static func find_child_by_type(node:Node,type,depth:int = 1,is_strict_type_check:bool = false) -> Node:
	var queue_init:Array[Node] = []
	var queue_res:Array[Node] = []
	var res:Node = null
	queue_init.append(node)
	while queue_init.size() >= 0 and depth != 0:
		if queue_init.size() == 0:
			if queue_res.size() > 0:
				queue_init.append_array(queue_res)
				queue_res.clear()
				depth -= 1
			else:
				break
		var temp:Node = queue_init.pop_front()
		var script = temp.get_script()
		if script:
			if typeof(type) == TYPE_OBJECT:
				var inheritance_chain_str:Array = Utils.get_inheritance_chain_str(script)
				if type.get_global_name() == inheritance_chain_str[0] if is_strict_type_check else type.get_global_name() in inheritance_chain_str:
					res = temp
					return res
			elif typeof(type) == TYPE_STRING :
				if type == script.get_global_name():
					res = temp
					return res
		else:
			if typeof(type) == TYPE_OBJECT:
				pass
			elif typeof(type) == TYPE_STRING and temp.get_class() == type if is_strict_type_check else temp.is_class(type):
				res = temp
				return res
		for child in temp.get_children():
			queue_res.push_back(child)
	return res
## 根据类型查找所有子节点
## node:当前节点
## type:查找的类型，可以是具体类型（只支持自定义类，即节点附加这个脚本），也可以是字符串（只支持非自定义类型和自定义类型的严格类型检查）
## depth:查找深度，默认只查找自身，<0会递归查找直至没有节点，>0会查找对应深度的子节点
## is_strict_type_check:是否是严格类型检查，如果为true，则节点必须是相同类型，继承类将无法通过检查，该参数只有传入的type为具体类型时有效
static func find_children_by_type(node:Node,type,depth:int = 1,is_strict_type_check:bool = false) -> Array[Node]:
	var queue_init:Array[Node] = []
	var queue_res:Array[Node] = []
	var res:Array[Node] = []
	queue_init.append(node)
	while queue_init.size() >= 0 and depth != 0:
		if queue_init.size() == 0:
			if queue_res.size() > 0:
				queue_init.append_array(queue_res)
				queue_res.clear()
				depth -= 1
			else:
				break
		var temp:Node = queue_init.pop_front()
		var script = temp.get_script()
		if script:
			if typeof(type) == TYPE_OBJECT:
				var inheritance_chain_str:Array = Utils.get_inheritance_chain_str(script)
				if type.get_global_name() == inheritance_chain_str[0] if is_strict_type_check else type.get_global_name() in inheritance_chain_str:
					res.append(temp)
			elif typeof(type) == TYPE_STRING :
				if type == script.get_global_name():
					res.append(temp)
		else:
			if typeof(type) == TYPE_OBJECT:
				pass
			elif typeof(type) == TYPE_STRING and temp.get_class() == type if is_strict_type_check else temp.is_class(type):
				res.append(temp)
		for child in temp.get_children():
			queue_res.push_back(child)
	return res
## 根据类型查找第一个父节点
## node:当前节点
## type:查找的类型，可以是具体类型（只支持自定义类，即节点附加这个脚本），也可以是字符串（只支持非自定义类型和自定义类型的严格类型检查）
## depth:查找深度，默认只查找直接父节点，<0会递归查找直至没有父节点，>0会查找对应深度的父节点
## is_strict_type_check:是否是严格类型检查，如果为true，则节点必须是相同类型，继承类将无法通过检查，该参数只有传入的type为具体类型时有效
static func find_parent_by_type(node:Node,type,depth:int = 1,is_strict_type_check:bool = false) -> Node:
	var parent = node.get_parent()
	var res:Node = null
	while parent:
		depth -= 1
		if depth == 0:
			break
		var script = parent.get_script()
		if script:
			if typeof(type) == TYPE_OBJECT:
				var inheritance_chain_str:Array = Utils.get_inheritance_chain_str(script)
				if type.get_global_name() == inheritance_chain_str[0] if is_strict_type_check else type.get_global_name() in inheritance_chain_str:
					res = parent
					break
			elif typeof(type) == TYPE_STRING :
				if type == script.get_global_name():
					res = parent
					break
		else:
			if typeof(type) == TYPE_OBJECT:
				pass
			elif typeof(type) == TYPE_STRING and parent.get_class() == type if is_strict_type_check else parent.is_class(type):
				res = parent
				break
		parent = parent.get_parent()
	return res
## 根据类型查找所有父节点
## node:当前节点
## type:查找的类型，可以是具体类型（只支持自定义类，即节点附加这个脚本），也可以是字符串（只支持非自定义类型和自定义类型的严格类型检查）
## depth:查找深度，默认只查找直接父节点，<0会递归查找直至没有节点，>0会查找对应深度的父节点
## is_strict_type_check:是否是严格类型检查，如果为true，则节点必须是相同类型，继承类将无法通过检查，该参数只有传入的type为具体类型时有效
static func find_parents_by_type(node:Node,type,depth:int = 1,is_strict_type_check:bool = false) -> Array[Node]:
	var parent = node.get_parent()
	var res:Array[Node] = []
	while parent:
		depth -= 1
		if depth == 0:
			break
		var script = parent.get_script()
		if script:
			if typeof(type) == TYPE_OBJECT:
				var inheritance_chain_str:Array = Utils.get_inheritance_chain_str(script)
				if type.get_global_name() == inheritance_chain_str[0] if is_strict_type_check else type.get_global_name() in inheritance_chain_str:
					res.append(parent)
			elif typeof(type) == TYPE_STRING :
				if type == script.get_global_name():
					res.append(parent)
		else:
			if typeof(type) == TYPE_OBJECT:
				pass
			elif typeof(type) == TYPE_STRING and parent.get_class() == type if is_strict_type_check else parent.is_class(type):
				res.append(parent)
		parent = parent.get_parent()
	return res
#endregion
